import { createHash } from 'crypto';
import { Construct, Node } from 'constructs';
import { AliasOptions } from './alias';
import { Architecture } from './architecture';
import { EventInvokeConfig, EventInvokeConfigOptions } from './event-invoke-config';
import { IEventSource } from './event-source';
import { EventSourceMapping, EventSourceMappingOptions } from './event-source-mapping';
import { FunctionUrlAuthType, FunctionUrlOptions, FunctionUrl } from './function-url';
import { IVersion } from './lambda-version';
import { CfnPermission } from './lambda.generated';
import { Permission } from './permission';
import { addAlias, flatMap } from './util';
import * as cloudwatch from '../../aws-cloudwatch';
import * as ec2 from '../../aws-ec2';
import * as iam from '../../aws-iam';
import { Annotations, ArnFormat, IResource, Resource, Token, Stack } from '../../core';

export interface IFunction extends IResource, ec2.IConnectable, iam.IGrantable {

  /**
   * The name of the function.
   *
   * @attribute
   */
  readonly functionName: string;

  /**
   * The ARN of the function.
   *
   * @attribute
   */
  readonly functionArn: string;

  /**
   * The IAM role associated with this function.
   */
  readonly role?: iam.IRole;

  /**
   * Whether or not this Lambda function was bound to a VPC
   *
   * If this is is `false`, trying to access the `connections` object will fail.
   */
  readonly isBoundToVpc: boolean;

  /**
   * The `$LATEST` version of this function.
   *
   * Note that this is reference to a non-specific AWS Lambda version, which
   * means the function this version refers to can return different results in
   * different invocations.
   *
   * To obtain a reference to an explicit version which references the current
   * function configuration, use `lambdaFunction.currentVersion` instead.
   */
  readonly latestVersion: IVersion;

  /**
   * The construct node where permissions are attached.
   */
  readonly permissionsNode: Node;

  /**
   * The system architectures compatible with this lambda function.
   */
  readonly architecture: Architecture;

  /**
   * The ARN(s) to put into the resource field of the generated IAM policy for grantInvoke().
   *
   * This property is for cdk modules to consume only. You should not need to use this property.
   * Instead, use grantInvoke() directly.
   */
  readonly resourceArnsForGrantInvoke: string[];

  /**
   * Adds an event source that maps to this AWS Lambda function.
   * @param id construct ID
   * @param options mapping options
   */
  addEventSourceMapping(id: string, options: EventSourceMappingOptions): EventSourceMapping;

  /**
   * Adds a permission to the Lambda resource policy.
   * @param id The id for the permission construct
   * @param permission The permission to grant to this Lambda function. @see Permission for details.
   */
  addPermission(id: string, permission: Permission): void;

  /**
   * Adds a statement to the IAM role assumed by the instance.
   */
  addToRolePolicy(statement: iam.PolicyStatement): void;

  /**
   * Grant the given identity permissions to invoke this Lambda
   */
  grantInvoke(identity: iam.IGrantable): iam.Grant;

  /**
   * Grant the given identity permissions to invoke this Lambda Function URL
   */
  grantInvokeUrl(identity: iam.IGrantable): iam.Grant;

  /**
   * Grant multiple principals the ability to invoke this Lambda via CompositePrincipal
   */
  grantInvokeCompositePrincipal(compositePrincipal: iam.CompositePrincipal): iam.Grant[];

  /**
   * Return the given named metric for this Lambda
   */
  metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric;

  /**
   * Metric for the Duration of this Lambda
   *
   * @default average over 5 minutes
   */
  metricDuration(props?: cloudwatch.MetricOptions): cloudwatch.Metric;

  /**
   * Metric for the number of invocations of this Lambda
   *
   * @default sum over 5 minutes
   */
  metricInvocations(props?: cloudwatch.MetricOptions): cloudwatch.Metric;

  /**
   * Metric for the number of throttled invocations of this Lambda
   *
   * @default sum over 5 minutes
   */
  metricThrottles(props?: cloudwatch.MetricOptions): cloudwatch.Metric;

  /**
   * Adds an event source to this function.
   *
   * Event sources are implemented in the aws-cdk-lib/aws-lambda-event-sources module.
   *
   * The following example adds an SQS Queue as an event source:
   * ```
   * import { SqsEventSource } from 'aws-cdk-lib/aws-lambda-event-sources';
   * myFunction.addEventSource(new SqsEventSource(myQueue));
   * ```
   */
  addEventSource(source: IEventSource): void;

  /**
   * Configures options for asynchronous invocation.
   */
  configureAsyncInvoke(options: EventInvokeConfigOptions): void;

  /**
   * Adds a url to this lambda function.
   */
  addFunctionUrl(options?: FunctionUrlOptions): FunctionUrl;
}

/**
 * Represents a Lambda function defined outside of this stack.
 */
export interface FunctionAttributes {
  /**
   * The ARN of the Lambda function.
   *
   * Format: arn:<partition>:lambda:<region>:<account-id>:function:<function-name>
   */
  readonly functionArn: string;

  /**
   * The IAM execution role associated with this function.
   *
   * If the role is not specified, any role-related operations will no-op.
   */
  readonly role?: iam.IRole;

  /**
   * Id of the security group of this Lambda, if in a VPC.
   *
   * This needs to be given in order to support allowing connections
   * to this Lambda.
   *
   * @deprecated use `securityGroup` instead
   */
  readonly securityGroupId?: string;

  /**
   * The security group of this Lambda, if in a VPC.
   *
   * This needs to be given in order to support allowing connections
   * to this Lambda.
   */
  readonly securityGroup?: ec2.ISecurityGroup;

  /**
   * Setting this property informs the CDK that the imported function is in the same environment as the stack.
   * This affects certain behaviours such as, whether this function's permission can be modified.
   * When not configured, the CDK attempts to auto-determine this. For environment agnostic stacks, i.e., stacks
   * where the account is not specified with the `env` property, this is determined to be false.
   *
   * Set this to property *ONLY IF* the imported function is in the same account as the stack
   * it's imported in.
   * @default - depends: true, if the Stack is configured with an explicit `env` (account and region) and the account is the same as this function.
   * For environment-agnostic stacks this will default to `false`.
   */
  readonly sameEnvironment?: boolean;

  /**
   * Setting this property informs the CDK that the imported function ALREADY HAS the necessary permissions
   * for what you are trying to do. When not configured, the CDK attempts to auto-determine whether or not
   * additional permissions are necessary on the function when grant APIs are used. If the CDK tried to add
   * permissions on an imported lambda, it will fail.
   *
   * Set this property *ONLY IF* you are committing to manage the imported function's permissions outside of
   * CDK. You are acknowledging that your CDK code alone will have insufficient permissions to access the
   * imported function.
   *
   * @default false
   */
  readonly skipPermissions?: boolean;

  /**
   * The architecture of this Lambda Function (this is an optional attribute and defaults to X86_64).
   * @default - Architecture.X86_64
   */
  readonly architecture?: Architecture;
}

export abstract class FunctionBase extends Resource implements IFunction, ec2.IClientVpnConnectionHandler {
  /**
   * The principal this Lambda Function is running as
   */
  public abstract readonly grantPrincipal: iam.IPrincipal;

  /**
   * The name of the function.
   */
  public abstract readonly functionName: string;

  /**
   * The ARN fo the function.
   */
  public abstract readonly functionArn: string;

  /**
   * The IAM role associated with this function.
   *
   * Undefined if the function was imported without a role.
   */
  public abstract readonly role?: iam.IRole;

  /**
   * The construct node where permissions are attached.
   */
  public abstract readonly permissionsNode: Node;

  /**
   * The architecture of this Lambda Function.
   */
  public abstract readonly architecture: Architecture;

  /**
   * Whether the addPermission() call adds any permissions
   *
   * True for new Lambdas, false for version $LATEST and imported Lambdas
   * from different accounts.
   */
  protected abstract readonly canCreatePermissions: boolean;

  /**
   * The ARN(s) to put into the resource field of the generated IAM policy for grantInvoke()
   */
  public abstract readonly resourceArnsForGrantInvoke: string[];

  /**
   * Whether the user decides to skip adding permissions.
   * The only use case is for cross-account, imported lambdas
   * where the user commits to modifying the permisssions
   * on the imported lambda outside CDK.
   * @internal
   */
  protected readonly _skipPermissions?: boolean;

  /**
   * Actual connections object for this Lambda
   *
   * May be unset, in which case this Lambda is not configured use in a VPC.
   * @internal
   */
  protected _connections?: ec2.Connections;

  private _latestVersion?: LatestVersion;

  /**
   * Flag to delay adding a warning message until current version is invoked.
   * @internal
   */
  protected _warnIfCurrentVersionCalled: boolean = false;

  /**
   * Mapping of invocation principals to grants. Used to de-dupe `grantInvoke()` calls.
   * @internal
   */
  protected _invocationGrants: Record<string, iam.Grant> = {};

  /**
   * Mapping of fucntion URL invocation principals to grants. Used to de-dupe `grantInvokeUrl()` calls.
   * @internal
   */
  protected _functionUrlInvocationGrants: Record<string, iam.Grant> = {};

  /**
   * A warning will be added to functions under the following conditions:
   * - permissions that include `lambda:InvokeFunction` are added to the unqualified function.
   * - function.currentVersion is invoked before or after the permission is created.
   *
   * This applies only to permissions on Lambda functions, not versions or aliases.
   * This function is overridden as a noOp for QualifiedFunctionBase.
   */
  public considerWarningOnInvokeFunctionPermissions(scope: Construct, action: string) {
    const affectedPermissions = ['lambda:InvokeFunction', 'lambda:*', 'lambda:Invoke*'];
    if (affectedPermissions.includes(action)) {
      if (scope.node.tryFindChild('CurrentVersion')) {
        this.warnInvokeFunctionPermissions(scope);
      } else {
        this._warnIfCurrentVersionCalled = true;
      }
    }
  }

  protected warnInvokeFunctionPermissions(scope: Construct): void {
    Annotations.of(scope).addWarningV2('@aws-cdk/aws-lambda:addPermissionsToVersionOrAlias', [
      "AWS Lambda has changed their authorization strategy, which may cause client invocations using the 'Qualifier' parameter of the lambda function to fail with Access Denied errors.",
      "If you are using a lambda Version or Alias, make sure to call 'grantInvoke' or 'addPermission' on the Version or Alias, not the underlying Function",
      'See: https://github.com/aws/aws-cdk/issues/19273',
    ].join('\n'));
  }

  /**
   * Adds a permission to the Lambda resource policy.
   * @param id The id for the permission construct
   * @param permission The permission to grant to this Lambda function. @see Permission for details.
   */
  public addPermission(id: string, permission: Permission) {
    if (!this.canCreatePermissions) {
      Annotations.of(this).addWarningV2('UnclearLambdaEnvironment', `addPermission() has no effect on a Lambda Function with region=${this.env.region}, account=${this.env.account}, in a Stack with region=${Stack.of(this).region}, account=${Stack.of(this).account}. Suppress this warning if this is is intentional, or pass sameEnvironment=true to fromFunctionAttributes() if you would like to add the permissions.`);
      return;
    }

    let principal = this.parsePermissionPrincipal(permission.principal);

    let { sourceArn, sourceAccount, principalOrgID } = this.validateConditionCombinations(permission.principal) ?? {};

    const action = permission.action ?? 'lambda:InvokeFunction';
    const scope = permission.scope ?? this;

    this.considerWarningOnInvokeFunctionPermissions(scope, action);

    new CfnPermission(scope, id, {
      action,
      principal,
      functionName: this.functionArn,
      eventSourceToken: permission.eventSourceToken,
      sourceAccount: permission.sourceAccount ?? sourceAccount,
      sourceArn: permission.sourceArn ?? sourceArn,
      principalOrgId: permission.organizationId ?? principalOrgID,
      functionUrlAuthType: permission.functionUrlAuthType,
    });
  }

  /**
   * Adds a statement to the IAM role assumed by the instance.
   */
  public addToRolePolicy(statement: iam.PolicyStatement) {
    if (!this.role) {
      return;
    }

    this.role.addToPrincipalPolicy(statement);
  }

  /**
   * Access the Connections object
   *
   * Will fail if not a VPC-enabled Lambda Function
   */
  public get connections(): ec2.Connections {
    if (!this._connections) {
      // eslint-disable-next-line max-len
      throw new Error('Only VPC-associated Lambda Functions have security groups to manage. Supply the "vpc" parameter when creating the Lambda, or "securityGroupId" when importing it.');
    }
    return this._connections;
  }

  public get latestVersion(): IVersion {
    if (!this._latestVersion) {
      this._latestVersion = new LatestVersion(this);
    }
    return this._latestVersion;
  }

  /**
   * Whether or not this Lambda function was bound to a VPC
   *
   * If this is is `false`, trying to access the `connections` object will fail.
   */
  public get isBoundToVpc(): boolean {
    return !!this._connections;
  }

  public addEventSourceMapping(id: string, options: EventSourceMappingOptions): EventSourceMapping {
    return new EventSourceMapping(this, id, {
      target: this,
      ...options,
    });
  }

  /**
   * Grant the given identity permissions to invoke this Lambda
   */
  public grantInvoke(grantee: iam.IGrantable): iam.Grant {
    const hash = createHash('sha256')
      .update(JSON.stringify({
        principal: grantee.grantPrincipal.toString(),
        conditions: grantee.grantPrincipal.policyFragment.conditions,
      }), 'utf8')
      .digest('base64');
    const identifier = `Invoke${hash}`;

    // Memoize the result so subsequent grantInvoke() calls are idempotent
    let grant = this._invocationGrants[identifier];
    if (!grant) {
      grant = this.grant(grantee, identifier, 'lambda:InvokeFunction', this.resourceArnsForGrantInvoke);
      this._invocationGrants[identifier] = grant;
    }
    return grant;
  }

  /**
   * Grant the given identity permissions to invoke this Lambda Function URL
   */
  public grantInvokeUrl(grantee: iam.IGrantable): iam.Grant {
    const identifier = `InvokeFunctionUrl${grantee.grantPrincipal}`; // calls the .toString() of the principal

    // Memoize the result so subsequent grantInvoke() calls are idempotent
    let grant = this._functionUrlInvocationGrants[identifier];
    if (!grant) {
      grant = this.grant(grantee, identifier, 'lambda:InvokeFunctionUrl', [this.functionArn], {
        functionUrlAuthType: FunctionUrlAuthType.AWS_IAM,
      });
      this._functionUrlInvocationGrants[identifier] = grant;
    }
    return grant;
  }

  /**
   * Grant multiple principals the ability to invoke this Lambda via CompositePrincipal
   */
  public grantInvokeCompositePrincipal(compositePrincipal: iam.CompositePrincipal): iam.Grant[] {
    return compositePrincipal.principals.map((principal) => this.grantInvoke(principal));
  }

  public addEventSource(source: IEventSource) {
    source.bind(this);
  }

  public configureAsyncInvoke(options: EventInvokeConfigOptions): void {
    if (this.node.tryFindChild('EventInvokeConfig') !== undefined) {
      throw new Error(`An EventInvokeConfig has already been configured for the function at ${this.node.path}`);
    }

    new EventInvokeConfig(this, 'EventInvokeConfig', {
      function: this,
      ...options,
    });
  }

  public addFunctionUrl(options?: FunctionUrlOptions): FunctionUrl {
    return new FunctionUrl(this, 'FunctionUrl', {
      function: this,
      ...options,
    });
  }

  /**
   * Returns the construct tree node that corresponds to the lambda function.
   * For use internally for constructs, when the tree is set up in non-standard ways. Ex: SingletonFunction.
   * @internal
   */
  protected _functionNode(): Node {
    return this.node;
  }

  /**
   * Given the function arn, check if the account id matches this account
   *
   * Function ARNs look like this:
   *
   *   arn:aws:lambda:region:account-id:function:function-name
   *
   * ..which means that in order to extract the `account-id` component from the ARN, we can
   * split the ARN using ":" and select the component in index 4.
   *
   * @returns true if account id of function matches the account specified on the stack, false otherwise.
   *
   * @internal
   */
  protected _isStackAccount(): boolean {
    if (Token.isUnresolved(this.stack.account) || Token.isUnresolved(this.functionArn)) {
      return false;
    }
    return this.stack.splitArn(this.functionArn, ArnFormat.SLASH_RESOURCE_NAME).account === this.stack.account;
  }

  private grant(
    grantee: iam.IGrantable,
    identifier:string,
    action: string,
    resourceArns: string[],
    permissionOverrides?: Partial<Permission>,
  ): iam.Grant {
    const grant = iam.Grant.addToPrincipalOrResource({
      grantee,
      actions: [action],
      resourceArns,

      // Fake resource-like object on which to call addToResourcePolicy(), which actually
      // calls addPermission()
      resource: {
        addToResourcePolicy: (_statement) => {
          // Couldn't add permissions to the principal, so add them locally.
          this.addPermission(identifier, {
            principal: grantee.grantPrincipal!,
            action: action,
            ...permissionOverrides,
          });

          const permissionNode = this._functionNode().tryFindChild(identifier);
          if (!permissionNode && !this._skipPermissions) {
            throw new Error('Cannot modify permission to lambda function. Function is either imported or $LATEST version.\n'
              + 'If the function is imported from the same account use `fromFunctionAttributes()` API with the `sameEnvironment` flag.\n'
              + 'If the function is imported from a different account and already has the correct permissions use `fromFunctionAttributes()` API with the `skipPermissions` flag.');
          }
          return { statementAdded: true, policyDependable: permissionNode };
        },
        node: this.node,
        stack: this.stack,
        env: this.env,
        applyRemovalPolicy: this.applyRemovalPolicy,
      },
    });

    return grant;
  }

  /**
   * Translate IPrincipal to something we can pass to AWS::Lambda::Permissions
   *
   * Do some nasty things because `Permission` supports a subset of what the
   * full IAM principal language supports, and we may not be able to parse strings
   * outright because they may be tokens.
   *
   * Try to recognize some specific Principal classes first, then try a generic
   * fallback.
   */
  private parsePermissionPrincipal(principal: iam.IPrincipal | { readonly wrapped: iam.IPrincipal }) {
    // Try some specific common classes first.
    // use duck-typing, not instance of
    if ('wrapped' in principal) {
      // eslint-disable-next-line dot-notation
      principal = principal['wrapped'];
    }

    if ('accountId' in principal) {
      return (principal as iam.AccountPrincipal).accountId;
    }

    if ('service' in principal) {
      return (principal as iam.ServicePrincipal).service;
    }

    if ('arn' in principal) {
      return (principal as iam.ArnPrincipal).arn;
    }

    const stringEquals = matchSingleKey('StringEquals', principal.policyFragment.conditions);
    if (stringEquals) {
      const orgId = matchSingleKey('aws:PrincipalOrgID', stringEquals);
      if (orgId) {
        // we will move the organization id to the `principalOrgId` property of `Permissions`.
        return '*';
      }
    }

    // Try a best-effort approach to support simple principals that are not any of the predefined
    // classes, but are simple enough that they will fit into the Permission model. Main target
    // here: imported Roles, Users, Groups.
    //
    // The principal cannot have conditions and must have a single { AWS: [arn] } entry.
    const json = principal.policyFragment.principalJson;
    if (Object.keys(principal.policyFragment.conditions).length === 0 && json.AWS) {
      if (typeof json.AWS === 'string') { return json.AWS; }
      if (Array.isArray(json.AWS) && json.AWS.length === 1 && typeof json.AWS[0] === 'string') {
        return json.AWS[0];
      }
    }

    throw new Error(`Invalid principal type for Lambda permission statement: ${principal.constructor.name}. ` +
      'Supported: AccountPrincipal, ArnPrincipal, ServicePrincipal, OrganizationPrincipal');

    /**
     * Returns the value at the key if the object contains the key and nothing else. Otherwise,
     * returns undefined.
     */
    function matchSingleKey(key: string, obj: Record<string, any>): any | undefined {
      if (Object.keys(obj).length !== 1) { return undefined; }

      return obj[key];
    }

  }

  private validateConditionCombinations(principal: iam.IPrincipal): {
    sourceArn: string | undefined;
    sourceAccount: string | undefined;
    principalOrgID: string | undefined;
  } | undefined {
    const conditions = this.validateConditions(principal);

    if (!conditions) { return undefined; }

    const sourceArn = requireString(requireObject(conditions.ArnLike)?.['aws:SourceArn']);
    const sourceAccount = requireString(requireObject(conditions.StringEquals)?.['aws:SourceAccount']);
    const principalOrgID = requireString(requireObject(conditions.StringEquals)?.['aws:PrincipalOrgID']);

    // PrincipalOrgID cannot be combined with any other conditions
    if (principalOrgID && (sourceArn || sourceAccount)) {
      throw new Error('PrincipalWithConditions had unsupported condition combinations for Lambda permission statement: principalOrgID cannot be set with other conditions.');
    }

    return {
      sourceArn,
      sourceAccount,
      principalOrgID,
    };
  }

  private validateConditions(principal: iam.IPrincipal): iam.Conditions | undefined {
    if (this.isPrincipalWithConditions(principal)) {
      const conditions: iam.Conditions = principal.policyFragment.conditions;
      const conditionPairs = flatMap(
        Object.entries(conditions),
        ([operator, conditionObjs]) => Object.keys(conditionObjs as object).map(key => { return { operator, key }; }),
      );

      // These are all the supported conditions. Some combinations are not supported,
      // like only 'aws:SourceArn' or 'aws:PrincipalOrgID' and 'aws:SourceAccount'.
      // These will be validated through `this.validateConditionCombinations`.
      const supportedPrincipalConditions = [{
        operator: 'ArnLike',
        key: 'aws:SourceArn',
      },
      {
        operator: 'StringEquals',
        key: 'aws:SourceAccount',
      }, {
        operator: 'StringEquals',
        key: 'aws:PrincipalOrgID',
      }];

      const unsupportedConditions = conditionPairs.filter(
        (condition) => !supportedPrincipalConditions.some(
          (supportedCondition) => supportedCondition.operator === condition.operator && supportedCondition.key === condition.key,
        ),
      );

      if (unsupportedConditions.length == 0) {
        return conditions;
      } else {
        throw new Error(`PrincipalWithConditions had unsupported conditions for Lambda permission statement: ${JSON.stringify(unsupportedConditions)}. ` +
          `Supported operator/condition pairs: ${JSON.stringify(supportedPrincipalConditions)}`);
      }
    }

    return undefined;
  }

  private isPrincipalWithConditions(principal: iam.IPrincipal): boolean {
    return Object.keys(principal.policyFragment.conditions).length > 0;
  }
}

export abstract class QualifiedFunctionBase extends FunctionBase {
  /** The underlying `IFunction` */
  public abstract readonly lambda: IFunction;

  public readonly permissionsNode = this.node;

  /**
   * The qualifier of the version or alias of this function.
   * A qualifier is the identifier that's appended to a version or alias ARN.
   * @see https://docs.aws.amazon.com/lambda/latest/dg/API_GetFunctionConfiguration.html#API_GetFunctionConfiguration_RequestParameters
   */
  protected abstract readonly qualifier: string;

  public get latestVersion() {
    return this.lambda.latestVersion;
  }

  public get resourceArnsForGrantInvoke() {
    return [this.functionArn];
  }

  public configureAsyncInvoke(options: EventInvokeConfigOptions): void {
    if (this.node.tryFindChild('EventInvokeConfig') !== undefined) {
      throw new Error(`An EventInvokeConfig has already been configured for the qualified function at ${this.node.path}`);
    }

    new EventInvokeConfig(this, 'EventInvokeConfig', {
      function: this.lambda,
      qualifier: this.qualifier,
      ...options,
    });
  }

  public considerWarningOnInvokeFunctionPermissions(_scope: Construct, _action: string): void {
    // noOp
    return;
  }
}

/**
 * The $LATEST version of a function, useful when attempting to create aliases.
 */
class LatestVersion extends FunctionBase implements IVersion {
  public readonly lambda: IFunction;
  public readonly version = '$LATEST';
  public readonly permissionsNode = this.node;

  protected readonly canCreatePermissions = false;

  constructor(lambda: FunctionBase) {
    super(lambda, '$LATEST');
    this.lambda = lambda;
  }

  public get functionArn() {
    return `${this.lambda.functionArn}:${this.version}`;
  }

  public get functionName() {
    return `${this.lambda.functionName}:${this.version}`;
  }

  public get architecture() {
    return this.lambda.architecture;
  }

  public get grantPrincipal() {
    return this.lambda.grantPrincipal;
  }

  public get latestVersion() {
    return this;
  }

  public get role() {
    return this.lambda.role;
  }

  public get edgeArn(): never {
    throw new Error('$LATEST function version cannot be used for Lambda@Edge');
  }

  public get resourceArnsForGrantInvoke() {
    return [this.functionArn];
  }

  public addAlias(aliasName: string, options: AliasOptions = {}) {
    return addAlias(this, this, aliasName, options);
  }
}

function requireObject(x: unknown): Record<string, unknown> | undefined {
  return x && typeof x === 'object' && !Array.isArray(x) ? x as any : undefined;
}

function requireString(x: unknown): string | undefined {
  return x && typeof x === 'string' ? x : undefined;
}
